package parser

import (
	"bytes"
	"mi/compiler/ast"
	"mi/compiler/diagnostic"
	"mi/compiler/scanner"
	"mi/compiler/token"
)

const tabSize = 4
func (p *Parser) parseDocument(parent ast.Node, start int, length int) ast.Document {
	var saveParseDiagnosticsLength = len(p.parseDiagnostics)

	var ctx = &documentParser{parser: p, tabLevel: -1}
	var comment = ctx.parseDocument(start, length)
	if !isNull(comment) {
		comment.SetParent(parent)
	}

	p.parseDiagnostics = p.parseDiagnostics[:saveParseDiagnosticsLength]

	return comment
}

type documentParser struct {
	parser *Parser

	start    int
	end      int
	length   int
	tabLevel int

	content []byte
}

func (dp *documentParser) parseDocument(start int, length int) ast.Document {
	dp.content = dp.parser.sourceText
	dp.start = start
	if dp.start  < 0 {
		dp.start = 0
	}

	if length < 0 {
		dp.end = len(dp.content)
	} else {
		dp.end = dp.start + length
	}
	dp.length = dp.end - dp.start

	assert(dp.start >= 0)
	assert(dp.start <= dp.end)
	assert(dp.end <= len(dp.content))
	// Check for //
	if !dp.isDocumentStart(dp.content, dp.start) {
		return nil
	}

	var node ast.Document
	// + 3 for leading /**, - 5 in total for /** */
	dp.scanner().ScanRange(dp.start + 3, dp.length - 5, func() interface{} {
		dp.nextDocumentToken()
		node = dp.parseDocumentWorker()
		return nil
	})

	return node
}

func (dp *documentParser) isDocumentStart(content []byte, start int) bool {
	return scanner.PeekEqual(content, start, 0, '/') != -1 &&
		scanner.PeekEqual(content, start, 1, '*') != -1 &&
		scanner.PeekEqual(content, start, 2, '*') != -1 &&
		scanner.PeekEqual(content, start, 3, '*') == -1
}

func (dp *documentParser) char() byte {
	return dp.content[dp.scanner().GetTokenPos()]
}

func (dp *documentParser) token() token.Token {
	return dp.scanner().GetToken()
}

func (dp *documentParser) scanner() *scanner.Scanner {
	return dp.parser.scanner
}

func (dp *documentParser) nextDocumentToken() token.Token {
	dp.scanner().ScanDocumentToken()
	return dp.parser.token()
}

func (dp *documentParser) got(kind token.Token) bool {
	if dp.token() == kind {
		dp.nextDocumentToken()
		return true
	}

	return false
}

func (dp *documentParser) want(kind token.Token) bool {
	if dp.token() == kind {
		dp.nextDocumentToken()
		return true
	}

	// Report specific message if provided with one.  Otherwise, report generic fallback message.
	dp.parser.errorAtCurrentToken(diagnostic.M_0_expected, kind.ToString())
	return false
}

func (dp *documentParser) tryGetTabLevel() int {
	return dp.parser.lookAhead(func() interface{} {
		return dp.skipWhitespace()/tabSize
	}).(int)
}

func (dp *documentParser) skipWhitespace() int {
	var indent = 0
	for dp.token() == token.WhitespaceTrivia {
		var pos = dp.scanner().GetTokenPos()
		switch dp.content[pos] {
		case ' ':
			indent ++
		// TODO: assume that they are all tabs
		case '\t', '\v', '\f':
			indent += tabSize
		}

		dp.nextDocumentToken()
	}

	return indent
}

func (dp *documentParser) skipLineStartTrivia() {
	dp.parser.tryParseBool(func() bool {
		dp.skipWhitespace()
		if dp.token() != token.Asterisk {
			return false
		}
		dp.nextDocumentToken()
		return true
	})
}

func (dp *documentParser) forwardToNewLine() {
	for dp.token() != token.NewLineTrivia &&
		dp.token() != token.EndOfFile {
		dp.nextDocumentToken()
	}
}

func (dp *documentParser) parseEntityName() ast.Expression {
	var entity ast.Expression = dp.parseIdentifierName(nil)
	for dp.got(token.Dot) {
		var node = new(ast.QualifiedName)
		dp.parser.scope(node, func() {
			node.Left = entity
			node.Right = dp.parseIdentifierName(nil)
		}, entity.Pos())
		entity = node
	}
	return entity
}

func (dp *documentParser) parseExpression() ast.Expression {
	var entity ast.Expression = dp.parseIdentifierName(nil)
	for true {
		if dp.got(token.Dot) {
			var node = new(ast.SelectorExpression)
			dp.parser.scope(node, func() {
				node.Expression = entity
				node.Name = dp.parseIdentifierName(nil)
			}, entity.Pos())
			entity = node
			continue
		}

		if dp.got(token.Hash) {
			var node = new(ast.InstanceAccessExpression)
			dp.parser.scope(node, func() {
				node.Left = entity
				node.Right = dp.parseIdentifierName(nil)
			}, entity.Pos())
			entity = node
			continue
		}

		break
	}

	if dp.token() == token.OpenParen {
		var node = new(ast.DocumentCallExpression)
		dp.parser.scope(node, func() {
			dp.want(token.OpenParen)
			node.Expression = entity
			node.Arguments = dp.parser.parseDocumentTypeList()
			dp.want(token.CloseParen)
		}, entity.Pos())
		entity = node
	}

	return entity
}

func (dp *documentParser) isIdentifier() bool {
	return dp.token() == token.Identifier || dp.token().IsKeyword()
}

func (dp *documentParser) parseIdentifierName(message *diagnostic.Message) *ast.Identifier {
	if !scanner.TokenIsIdentifierOrKeyword(dp.token()) {
		var diagnosticMessage = message
		if diagnosticMessage == nil {
			diagnosticMessage = diagnostic.M_Identifier_expected
		}

		var reportAtCurrentPosition = message != nil
		if reportAtCurrentPosition {
			dp.parser.errorAtPosition(dp.parser.startPos(), 0, diagnosticMessage)
		} else {
			dp.parser.errorAtCurrentToken(diagnosticMessage)
		}

		// Missing token (pos == end)
		var node = new(ast.Identifier)
		dp.parser.fillMissPos(node)
		return node
	}

	var node = new(ast.Identifier)
	dp.parser.scope(node, func() {
		node.OriginalToken = dp.token()
		node.Value = dp.scanner().GetTokenText()
		dp.nextDocumentToken()
	}, dp.scanner().GetTokenPos())
	return node
}

func (dp *documentParser) parseDocumentWorker() ast.Document {
	var node = new(ast.DocRoot)
	dp.parser.scope(node, func() {
		node.Children = new(ast.DocumentList)
		dp.parser.listScope(node.Children, func() {
			for dp.token() != token.EndOfFile {
				node.Children.Add(dp.parseBlock())
			}
		})
	})

	return node
}

func (dp *documentParser) isStartOfHeader() bool {
	return dp.parser.lookAheadBool(func() bool {
		if dp.skipWhitespace() <= 3 {
			switch dp.token() {
			case token.Hash,
				token.HashHash,
				token.HashHashHash,
				token.HashHashHashHash,
				token.HashHashHashHashHash:
				return dp.nextDocumentToken() == token.WhitespaceTrivia
			}
		}

		return false
	})
}

func (dp *documentParser) parseHeader() *ast.Header {
	var node = new(ast.Header)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Token = dp.token()
		dp.nextDocumentToken()
		dp.skipWhitespace()
		node.Text = dp.parsePlainText(token.NewLineTrivia)
	})
	return node
}

func (dp *documentParser) isStartOfCodeBlock() bool {
	return dp.parser.lookAheadBool(func() bool {

		return dp.skipWhitespace() <= 3 &&
			dp.token() == token.BacktickBacktickBacktick
	})
}

func (dp *documentParser) isEndOfCodeBlock(indent int) bool {
	return dp.parser.lookAheadBool(func() bool {

		return dp.skipWhitespace() == indent &&
			dp.token() == token.BacktickBacktickBacktick
	})
}

func (dp *documentParser) parseCodeBlock() *ast.CodeBlock {
	var node = new(ast.CodeBlock)
	dp.parser.scope(node, func() {
		var indent = dp.skipWhitespace()
		dp.want(token.BacktickBacktickBacktick)
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Language = dp.parsePlainText(token.NewLineTrivia, token.EndOfFile)
		}

		node.Content = new(ast.DocumentList)
		dp.parser.listScope(node.Content, func() {
			for dp.token() != token.EndOfFile {
				dp.nextDocumentToken()
				dp.skipLineStartTrivia()
				if dp.isEndOfCodeBlock(indent) {
					break
				}

				// skip while trivia while indent
				var cur = 0
				loop: for cur < indent {
					switch dp.char() {
					case ' ':
						cur ++
					// TODO: assume that they are all tabs
					case '\t', '\v', '\f':
						cur += tabSize
					default:
						break loop
					}

					dp.nextDocumentToken()
				}

				if dp.token() != token.NewLineTrivia {
					node.Content.Add(dp.parsePlainText(token.NewLineTrivia, token.EndOfFile))
				}
				if dp.token() == token.NewLineTrivia {
					node.Content.Add(dp.parseLineBreak())
				}
			}
		})

		// It may be the end of the file here
		if dp.isEndOfCodeBlock(indent) {
			dp.skipWhitespace()
			dp.want(token.BacktickBacktickBacktick)
		}
	})

	return node
}

func (dp *documentParser) isStartOfListHelper(kind token.Token, cmp func(level int)bool) bool {
	return dp.parser.lookAheadBool(func() bool {
		var level = dp.skipWhitespace()/tabSize
		return cmp(level) && dp.token() == kind
	})
}

func (dp *documentParser) isStartOfUnorderedList(tabLevel int) bool {
	return dp.isStartOfListHelper(token.UnorderedListHead, func(level int) bool {
		return level - tabLevel ==  1 || level - tabLevel <= 0
	})
}

func (dp *documentParser) isStartOfUnorderedListOrHigher(tabLevel int) bool {
	return dp.isStartOfListHelper(token.UnorderedListHead, func(level int) bool {
		return  level - tabLevel == 1 || level - tabLevel <= 0
	})
}

func (dp *documentParser) parseUnorderedList() *ast.UnorderedList {
	var node = new(ast.UnorderedList)
	dp.parser.scope(node, func() {
		node.Level = dp.tryGetTabLevel()
		var saveTabLevel = node.Level
		dp.tabLevel = node.Level

		node.Content = new(ast.DocumentList)
		dp.parser.listScope(node.Content, func() {
			loop:
			for true {
				switch {
				case dp.isStartOfListItem(node.Level):
					node.Content.Add(dp.parseListItem())
				case dp.isStartOfOrderedList(node.Level):
					node.Content.Add(dp.parseOrderedList())
				case dp.isStartOfUnorderedList(node.Level):
					node.Content.Add(dp.parseUnorderedList())
				default:
					break loop
				}
			}
		})
		dp.tabLevel = saveTabLevel
	})
	return node
}

func (dp *documentParser) isStartOfOrderedList(tabLevel int) bool {
	return dp.isStartOfListHelper(token.OrderedListHead, func(level int) bool {
		return level - tabLevel == 1
	})
}

func (dp *documentParser) isStartOfOrderedListOrHigher(tabLevel int) bool {
	return dp.isStartOfListHelper(token.OrderedListHead, func(level int) bool {
		return level - tabLevel <= 0
	})
}

func (dp *documentParser) parseOrderedList() *ast.OrderedList {
	var node = new(ast.OrderedList)
	dp.parser.scope(node, func() {
		node.Level = dp.tryGetTabLevel()
		node.Content = new(ast.DocumentList)
		var saveTabLevel = node.Level
		dp.tabLevel = node.Level

		dp.parser.listScope(node.Content, func() {
		loop:
			for true {
				switch {
				case dp.isStartOfListItem(node.Level):
					node.Content.Add(dp.parseListItem())
				case dp.isStartOfOrderedList(node.Level):
					node.Content.Add(dp.parseOrderedList())
				case dp.isStartOfUnorderedList(node.Level):
					node.Content.Add(dp.parseUnorderedList())
				default:
					break loop
				}
			}
		})
		dp.tabLevel = saveTabLevel
	})
	return node
}

func (dp *documentParser) isStartOfListItem(tabLevel int) bool {
	return dp.parser.lookAheadBool(func() bool {
		var indent = dp.skipWhitespace()
		return indent/tabSize == tabLevel &&
			(dp.token() == token.OrderedListHead || dp.token() == token.UnorderedListHead)
	})
}

func (dp *documentParser) parseListItem() *ast.ListItem {
	var node = new(ast.ListItem)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		dp.nextDocumentToken()
		node.Content = dp.parseParagraph()
	})
	return node
}

func (dp *documentParser) parseBlock() ast.Document{
	if dp.token() == token.NewLineTrivia {
		dp.nextDocumentToken()
		dp.skipLineStartTrivia()
	}

	switch {
	case dp.isStartOfTag():
		return dp.parseTag()
	case dp.isStartOfHeader():
		return dp.parseHeader()
	case dp.isStartOfCodeBlock():
		return dp.parseCodeBlock()
	case dp.isStartOfLineBreak():
		return dp.parseLineBreak()
	case dp.isStartOfUnorderedList(-1):
		return dp.parseUnorderedList()
	case dp.isStartOfOrderedList(-1):
		return dp.parseOrderedList()
	default:
		dp.skipWhitespace()
		return dp.parseParagraph()
	}
}

func (dp *documentParser) parseParagraph() *ast.Paragraph {
	var node = new(ast.Paragraph)
	dp.parser.scope(node, func() {

		node.Content = new(ast.DocumentList)
		dp.parser.listScope(node.Content, func() {
			for true {
				if dp.token() == token.EndOfFile {
					break
				}

				if dp.token() == token.NewLineTrivia {
					if dp.isStartOfLineBreakOfNextLine() {
						break
					}

					dp.nextDocumentToken()
					dp.skipLineStartTrivia()

					// Jump out if you encounter block elements
					if dp.isStartOfTag() ||
						dp.isStartOfHeader() ||
						dp.isStartOfCodeBlock() ||
						dp.isStartOfListItem(dp.tabLevel) ||
						dp.isStartOfUnorderedListOrHigher(dp.tabLevel) ||
						dp.isStartOfOrderedListOrHigher(dp.tabLevel) {
						break
					}

					// The blank at the beginning of
					// the line is not valid content
					dp.skipWhitespace()
				}

				for true {
					switch dp.token() {
					case token.Asterisk:
						node.Content.Add(dp.parseItalic())
					case token.AsteriskAsterisk:
						node.Content.Add(dp.parseBold())
					case token.AsteriskAsteriskAsterisk:
						node.Content.Add(dp.parseBoldItalic())
					case token.Backtick:
						node.Content.Add(dp.parseCodeText())
					case token.OpenBracket:
						if linker := dp.tryParseLinkLike(); !isNull(linker){
							node.Content.Add(linker)
							break
						}
						fallthrough
					default:
						node.Content.Add(dp.parsePlainText(token.Asterisk, token.AsteriskAsterisk,
							token.AsteriskAsteriskAsterisk, token.Backtick, token.At, token.OpenBracket,
							token.NewLineTrivia, token.EndOfFile))
					}

					if dp.token() == token.NewLineTrivia || dp.token() == token.EndOfFile {
						break
					}
				}
			}
		})
	})

	return node
}

func (dp *documentParser) parseBold() *ast.Bold {
	var node = new(ast.Bold)
	dp.parser.scope(node, func() {
		dp.want(token.AsteriskAsterisk)
		node.Text = dp.parsePlainText(token.AsteriskAsterisk,
			token.NewLineTrivia, token.EndOfFile)
		dp.got(token.AsteriskAsterisk)
	})
	return node
}

func (dp *documentParser) parseItalic() *ast.Italic{
	var node = new(ast.Italic)
	dp.parser.scope(node, func() {
		dp.want(token.Asterisk)
		node.Text = dp.parsePlainText(token.Asterisk,
			token.NewLineTrivia, token.EndOfFile)
		dp.got(token.Asterisk)
	})
	return node
}

func (dp *documentParser) parseBoldItalic() *ast.BoldItalic {
	var node = new(ast.BoldItalic)
	dp.parser.scope(node, func() {
		dp.want(token.AsteriskAsteriskAsterisk)
		node.Text = dp.parsePlainText(token.AsteriskAsteriskAsterisk,
			token.NewLineTrivia, token.EndOfFile)
		dp.got(token.AsteriskAsteriskAsterisk)
	})
	return node
}

func (dp *documentParser) parseCodeText() *ast.CodeText {
	assert(dp.token() == token.Backtick)
	var node = new(ast.CodeText)
	dp.parser.listScope(node, func() {
		dp.want(token.Backtick)
		node.Content = dp.parsePlainText(token.Backtick,
			token.NewLineTrivia, token.EndOfFile)
		dp.got(token.Backtick)
	})
	return node
}

type linkKind int
const (
	none linkKind = iota
	link
	codeLink
	namedCodeLink
)

func (dp *documentParser) tryScanLinkKind() linkKind {
	var ahead = func() linkKind {
		if dp.token() != token.OpenBracket {
			return none
		}
		dp.nextDocumentToken()

		var forward = func(tok token.Token) {
			for dp.token() != tok &&
				dp.token() != token.NewLineTrivia &&
				dp.token() != token.EndOfFile {
				dp.nextDocumentToken()
			}
		}

		forward(token.CloseBracket)
		if dp.token() != token.CloseBracket {
			return none
		}
		dp.nextDocumentToken()
		if dp.token() != token.OpenParen && dp.token() != token.OpenBracket {
			return codeLink
		}

		if dp.token() == token.OpenParen {
			forward(token.CloseParen)
			if dp.token() != token.CloseParen {
				return none
			}
			return link
		} else {
			forward(token.CloseBracket)
			if dp.token() != token.CloseBracket {
				return none
			}
			dp.nextDocumentToken()

			// Cannot trail '[' or'('
			if dp.token() != token.OpenParen &&
				dp.token() != token.OpenBracket {
				return namedCodeLink
			}
		}

		return none
	}

	return dp.parser.lookAhead(func() interface{} {
		return ahead()
	}).(linkKind)
}

func (dp *documentParser) parseCodeLink(parseName bool) *ast.CodeLink {
	var node = new(ast.CodeLink)
	dp.parser.scope(node, func() {
		if parseName {
			dp.want(token.OpenBracket)
			node.Name = dp.parsePlainText(token.CloseBracket,
				token.NewLineTrivia, token.EndOfFile)
			dp.want(token.CloseBracket)
		}
		dp.want(token.OpenBracket)
		node.Expression = dp.parseExpression()
		dp.want(token.CloseBracket)
	})
	return node
}

func (dp *documentParser) parseLink() *ast.Link {
	var node = new(ast.Link)
	dp.parser.scope(node, func() {
		dp.want(token.OpenBracket)
		node.Name = dp.parsePlainText(token.CloseBracket,
			token.NewLineTrivia, token.EndOfFile)
		dp.want(token.CloseBracket)
		dp.want(token.OpenParen)
		node.Address = dp.parsePlainText(token.CloseParen,
			token.NewLineTrivia, token.EndOfFile)

		dp.want(token.CloseParen)
	})
	return node
}

func (dp *documentParser) tryParseLinkLike() ast.Document {
	switch dp.tryScanLinkKind() {
	case link:
		return dp.parseLink()
	case codeLink:
		return dp.parseCodeLink(false)
	case namedCodeLink:
		return dp.parseCodeLink(true)
	}

	return nil
}

func (dp *documentParser) isStartOfCodeTextOrCodeLiker() bool {
	return dp.token() == token.Backtick ||
		dp.token() == token.OpenBracket
}

// No termination judgment for the first character
func (dp *documentParser) parsePlainText(stopTokens ... token.Token) *ast.PlainText {
	var node = new(ast.PlainText)
	dp.parser.scope(node, func() {
		var buffer bytes.Buffer
		outer: for true {
			if dp.token() == token.StringLiteral {
				buffer.WriteString(dp.scanner().GetTokenValue())
			} else {
				buffer.WriteString(dp.scanner().GetTokenText())
			}
			dp.nextDocumentToken()

			for _, stopToken := range stopTokens {
				if dp.token() == stopToken {
					break outer
				}
			}
		}

		node.Value = buffer.String()
	})

	return node
}

func (dp *documentParser) isStartOfLineBreak() bool {
	return dp.parser.lookAheadBool(func() bool {
		dp.skipWhitespace()
		return dp.token() == token.NewLineTrivia
	})
}

func (dp *documentParser) isStartOfLineBreakOfNextLine() bool {
	return dp.parser.lookAheadBool(func() bool {
		if dp.token() != token.NewLineTrivia {
			return false
		}

		dp.nextDocumentToken()
		dp.skipLineStartTrivia()
		dp.skipWhitespace()
		return dp.token() == token.NewLineTrivia
	})
}

// Newline characters are not really consumed here
func (dp *documentParser) parseLineBreak() *ast.LineBreak {
	var node = new(ast.LineBreak)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
	})
	return node
}

func (dp *documentParser) isStartOfTag() bool {
	return dp.parser.lookAheadBool(func() bool {
		if dp.skipWhitespace() <= 3 && dp.token() == token.At{
			dp.nextDocumentToken()
			if dp.isIdentifier() {
				switch dp.scanner().GetTokenValue() {
				case ast.AuthorTagStr,
					ast.AliasTagStr,
					ast.ParameterTagStr,
					ast.ReturnTagStr,
					ast.ExceptionTagStr,
					ast.VersionTagStr,
					ast.SinceTagStr,
					ast.SeeTagStr,
					ast.DeprecatedTagStr:
					return true
				}
			}
		}

		return false
	})
}

func (dp *documentParser) parseTag() ast.Document {

	dp.skipWhitespace()
	var start = dp.scanner().GetTokenPos()
	dp.want(token.At)
	var tagName = dp.parseIdentifierName(nil)

	switch tagName.Value {
	case ast.AuthorTagStr:
		return dp.parseAuthorTag(start)
	case ast.AliasTagStr:
		return dp.parseAliasTag(start)
	case ast.ParameterTagStr:
		return dp.parseParameterTag(start)
	case ast.ReturnTagStr:
		return dp.parseReturnTag(start)
	case ast.ExceptionTagStr:
		return dp.parseExceptionTag(start)
	case ast.SeeTagStr:
		return dp.parseSeeTag(start)
	case ast.VersionTagStr:
		return dp.parseVersionTag(start)
	case ast.SinceTagStr:
		return dp.parseSinceTag(start)
	case ast.DeprecatedTagStr:
		dp.parser.hasDeprecatedTag = true
		return dp.parseDeprecatedTag(start)
	}
	return nil
}

func (dp *documentParser) parseAuthorTag(start int) *ast.AuthorTag {
	var node = new(ast.AuthorTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseAliasTag(start int) *ast.AliasTag {
	var node = new(ast.AliasTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Name = dp.parseIdentifierName(nil)
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseParameterTag(start int) *ast.ParameterTag {
	var node = new(ast.ParameterTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		if dp.token() == token.LessThan {
			node.IsTypeParameter = true
			dp.nextDocumentToken()
		}
		node.Name = dp.parseIdentifierName(nil)
		if node.IsTypeParameter {
			dp.want(token.GreaterThan)
		}
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseReturnTag(start int) *ast.ReturnTag {
	var node = new(ast.ReturnTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseExceptionTag(start int) *ast.ExceptionTag {
	var node = new(ast.ExceptionTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Type = dp.parser.parseDocumentType()
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseSeeTag(start int) *ast.SeeTag {
	var node = new(ast.SeeTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Expression = dp.parseExpression()
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseNumber() *ast.Number {
	if dp.token() == token.IntLiteral {
		var node = new(ast.Number)
		dp.parser.scope(node, func() {
			node.Value = dp.scanner().GetTokenValue()
			dp.nextDocumentToken()
		})
		return node
	}

	dp.parser.errorAtCurrentToken(diagnostic.M_Number_expected)
	var node = new(ast.Number)
	dp.parser.fillMissPos(node)
	return node
}

func (dp *documentParser) parseVersion() ast.Node {
	var entity ast.Node = dp.parseNumber()
	for dp.got(token.Dot) {
		var node = new(ast.Version)
		dp.parser.scope(node, func() {
			node.Left = entity
			node.Right = dp.parseNumber()
		}, entity.Pos())
		entity = node
	}
	return entity
}

func (dp *documentParser) parseVersionTag(start int) *ast.VersionTag {
	var node = new(ast.VersionTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Version = dp.parseVersion()
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseSinceTag(start int) *ast.SinceTag {
	var node = new(ast.SinceTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		node.Version = dp.parseVersion()
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}

func (dp *documentParser) parseDeprecatedTag(start int) *ast.DeprecatedTag {
	var node = new(ast.DeprecatedTag)
	dp.parser.scope(node, func() {
		dp.skipWhitespace()
		if dp.token() != token.NewLineTrivia {
			node.Content = dp.parseParagraph()
		}
	}, start)
	return node
}